package com.i72.basic.dynamic;

import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.FieldInfo;
import javassist.bytecode.annotation.*;

import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class JavassistCode {



    private static Object lock = new Object();
    private static ClassPool pool;

    private CtClass ctClass = null;

    /**
     * 当前添加的方法，用来支持连缀写法
     */
    private String currentMethod = null;
    private String currentField = null;
    //private Map<String,CtMethod> ctMethodMap = new HashMap<>();
    //private Map<String, CtField> ctFieldMap = new HashMap<>();
    private List<Annotation> annotationList = new ArrayList<>();
    private JavassistCode(){};


    private static ClassPool getPool(){

        if(pool==null){
            synchronized (lock){
                if(pool==null){
                    pool = ClassPool.getDefault();
                }
            }
        }

        return pool;

    }

    public static JavassistCode createClass(String className){
        return createClass(className,null);
    }

    public static JavassistCode createClass(String className, String superClassName){



        getPool();

        //先检查超类是否存在
        boolean existsSuperClass = false;
        if(!StringUtils.isEmpty(superClassName)){
            //先通过 Class.forClassName检测

            try {
                Class c = Class.forName(superClassName);
                if(c!=null){
                    existsSuperClass = true;
                }

                if(!existsSuperClass){
                    CtClass sc = pool.get(className);
                    if(sc!=null){
                        existsSuperClass = true;
                    }
                }

            }catch (Exception e){

            }


        }

        JavassistCode javassistHelper = new JavassistCode();

        try {
            CtClass ctClass = null;
            //CtClass ctClass = pool.get(className);

            //if (ctClass == null) {
               ctClass = pool.makeClass(className);

                if(existsSuperClass){
                    ctClass.setSuperclass(pool.get((superClassName)));
                }
            //}

            if(ctClass!=null){
                javassistHelper.ctClass = ctClass;
            }

        }catch (Exception e){

        }

        return javassistHelper;
    }

    public JavassistCode addInterface(String interfaceName){
        try {
            CtClass interfaceType = pool.get(interfaceName);
            this.ctClass.addInterface(interfaceType);
        }catch (Exception e){

        }
        return this;
    }

    public JavassistCode addMethod(String methodName,String methodCode){

        try {
            CtMethod method = null;
            method = CtNewMethod.make(methodCode, this.ctClass);
            this.ctClass.addMethod(method);
            this.currentMethod = methodName;
        }catch (Exception e){
            return null;
        }
        return this;
    }


    public JavassistCode addField(String fieldName,String fieldCode){

        try {
            CtField ctField = null;
            ctField = CtField.make(fieldCode, this.ctClass);
            this.ctClass.addField(ctField);
            this.currentField = fieldName;
        }catch (Exception e){

        }

        return this;
    }

    public JavassistCode addFieldAnnotation(String fieldName,String annotationName,Map annotationParam){
        ClassFile classFile = this.ctClass.getClassFile();
        ConstPool constpool = classFile.getConstPool();

        //AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
        Annotation annotation = new Annotation(annotationName, constpool);
        //annotationsAttribute.setAnnotation(annotation);

        if(annotationParam!=null && annotationParam.keySet().size()>0) {
            for(Object key:annotationParam.keySet()){

                Object value = annotationParam.get(key);

                annotation.addMemberValue(key.toString(),getMemberValue(value,constpool));

            }
        }

        this.annotationList.add(annotation);


        /*
        List<FieldInfo> fs = classFile.getFields();

        for(FieldInfo f:fs){
            if(f.getName().equals(fieldName)){
                f.addAttribute(annotationsAttribute);
                break;
            }
        }*/


        currentField = fieldName;

        return this;
    }

    public JavassistCode addFieldAnnotation(String annotationName,Map annotationParam) throws Exception{
        if(StringUtils.isEmpty(currentField)){
            throw new Exception("没有可操作的字段");
        }
        return this.addFieldAnnotation(currentField,annotationName,annotationParam);
    }

    public JavassistCode addFieldAnnotation(String annotationName) throws Exception{

        return this.addFieldAnnotation(annotationName,null);
    }

    public JavassistCode buildFieldAnnotation(){
        ClassFile classFile = this.ctClass.getClassFile();
        ConstPool constpool = classFile.getConstPool();
        AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
        Annotation [] arr = new Annotation[this.annotationList.size()];
        annotationsAttribute.setAnnotations(this.annotationList.toArray(arr));


        List<FieldInfo> fs = classFile.getFields();

        for(FieldInfo f:fs){
            if(f.getName().equals(this.currentField)){
                f.addAttribute(annotationsAttribute);
                this.annotationList.clear();
                break;
            }
        }


        return this;
    }

    public JavassistCode addMethodAnnotation(String methodName,String annotationName,Map annotationParam){
        ClassFile classFile = this.ctClass.getClassFile();
        ConstPool constpool = classFile.getConstPool();

        //AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
        Annotation annotation = new Annotation(annotationName, constpool);


        if(annotationParam!=null && annotationParam.keySet().size()>0) {
            for(Object key:annotationParam.keySet()){

                Object value = annotationParam.get(key);

                annotation.addMemberValue(key.toString(),getMemberValue(value,constpool));

            }
        }

        this.annotationList.add(annotation);

        //annotationsAttribute.setAnnotation(annotation);
        //classFile.getMethod(methodName).addAttribute(annotationsAttribute);

        currentMethod = methodName;

        return this;
    }

    public JavassistCode addMethodAnnotation(String annotationName,Map annotationParam) throws Exception{
        if(StringUtils.isEmpty(currentMethod)){
            throw new Exception("没有可操作的方法");
        }
        return this.addMethodAnnotation(currentMethod,annotationName,annotationParam);
    }


    public JavassistCode addMethodAnnotation(String annotationName) throws Exception{
        return this.addMethodAnnotation(annotationName,null);
    }

    public JavassistCode buildMethodAnnotation(){

        ClassFile classFile = this.ctClass.getClassFile();
        ConstPool constpool = classFile.getConstPool();

        AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);

        Annotation [] arr = new Annotation[this.annotationList.size()];
        annotationsAttribute.setAnnotations(this.annotationList.toArray(arr));

        classFile.getMethod(this.currentMethod).addAttribute(annotationsAttribute);

        this.annotationList.clear();

        return this;
    }

    public JavassistCode addAnnotation(String annotationName,Map annotationParam){

        ClassFile classFile = this.ctClass.getClassFile();
        ConstPool constpool = classFile.getConstPool();


        //AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
        Annotation annotation = new Annotation(annotationName, constpool);

        if(annotationParam!=null && annotationParam.keySet().size()>0) {
            for(Object key:annotationParam.keySet()){

                Object value = annotationParam.get(key);

                annotation.addMemberValue(key.toString(),getMemberValue(value,constpool));

            }
        }

        this.annotationList.add(annotation);


        //往注解设置值，根据注解的参数字段来设置即可。
        //annotation.addMemberValue("value", new IntegerMemberValue(classFile.getConstPool(), 0));
        //annotationsAttribute.setAnnotation(annotation);

        //this.ctClass.getClassFile().addAttribute(annotationsAttribute);

        return this;
    }

    public JavassistCode addAnnotation(String annotationName){
        return this.addAnnotation(annotationName,null);
    }

    public JavassistCode buildAnnotation(){

        ClassFile classFile = this.ctClass.getClassFile();
        ConstPool constpool = classFile.getConstPool();
        AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);

        Annotation [] arr = new Annotation[this.annotationList.size()];

        annotationsAttribute.setAnnotations(this.annotationList.toArray(arr));

        this.ctClass.getClassFile().addAttribute(annotationsAttribute);
        this.annotationList.clear();
        return this;
    }


    private MemberValue getMemberValue(Object value,ConstPool pool){

        Class c = value.getClass();

        MemberValue memberValue = null;

        switch (c.getName()){
            case "java.lang.String":memberValue = new StringMemberValue(value.toString(),pool);break;
            case "java.lang.Double":memberValue = new DoubleMemberValue((Double)value,pool);break;
            case "java.lang.Float":memberValue = new FloatMemberValue((Float)value,pool);break;
            case "java.lang.Integer":memberValue = new IntegerMemberValue((Integer) value,pool);break;
            case "java.lang.Long":memberValue = new LongMemberValue((Long)value,pool);break;
            default:
        }

        return memberValue;
    }



    public Class toClass(){

        //正常运行
        //注册bean
        //类加载器sun.misc.Launcher$AppClassLoader
        //他的父加载器是 sun.misc.Launcher$ExtClassLoader

        //获取bean
        //类加载器是org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader
        //他的父加载器是 sun.misc.Launcher$AppClassLoader

        //子加载器可以访问父加载器中的内容，所以正常是OK的

        //单元测试
        //注册bean
        //单元测试下的加载器是 sun.misc.Launcher$ExtClassLoader
        //他的父加载器是sun.misc.Launcher$ExtClassLoader

        //获取bean
        //org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader
        //sun.misc.Launcher$AppClassLoader

        /*
        final String LAUNCHED_CLASS_LOADER = "org.springframework.boot.loader.LaunchedURLClassLoader";
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        if (!LAUNCHED_CLASS_LOADER.equals(contextClassLoader.getClass().getName())) {
            if (LAUNCHED_CLASS_LOADER.equals(contextClassLoader.getParent().getClass().getName())) {
                contextClassLoader = contextClassLoader.getParent();
            } else {
                contextClassLoader = ClassLoader.getSystemClassLoader();
            }
        }*/
        // 传入类加载器

        if(this.ctClass!=null){
            try {
                //return this.ctClass.toClass(contextClassLoader, null);
                return this.ctClass.toClass();
            }catch (Exception e){
                return null;
            }
        }else{
            return null;
        }
    }

    public String anaylzeMethodParams(Method method){
        Class [] params = method.getParameterTypes();
        List<String> paramsList = new ArrayList<>();
        if(params!=null && params.length>0) {

            for(int i=0;i<params.length;i++) {
                Class p = params[i];
                paramsList.add(p.getName()+" "+"args"+i);
            }
        }
        return String.join(",",paramsList);
    }

    public String anaylzeMethod(Method method,String code){
        StringBuilder stringBuilder = new StringBuilder(500);

        stringBuilder.append("public ");
        stringBuilder.append(method.getReturnType().getName()+" ");
        stringBuilder.append(method.getName());
        stringBuilder.append("(");

        stringBuilder.append(anaylzeMethodParams(method));

        stringBuilder.append(")");
        stringBuilder.append("{ ");
        stringBuilder.append(code);
        stringBuilder.append(" }");

        return stringBuilder.toString();
    }
}

